﻿using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Data.Entity;
using System.Data;
using System.Linq;
using System.Text;
using System.Transactions;
using NUnit.Framework;
using DomainDriver.DomainModeling.Constraints;
using DomainDriver.DomainModeling.DataProviders;
using DomainDriver.DomainModeling.DomainModels;

namespace DomainDriver.DomainModeling.GenericUnitTesting
{
    // NOTE: Add "[TestFixture]" to sub-classes in actual test assembly

    public abstract class DomainModelTestFixture<DOMAIN_MODEL>
        where DOMAIN_MODEL : class, IDomainModel
    {
        private DataSourcePersistenceType m_PersistenceType;
        private bool m_BuildModelWithConstraints;
        private bool m_CreateNewDataStore;
        private DOMAIN_MODEL m_DomainModel;

        public DomainModelTestFixture(DataSourcePersistenceType persistenceType)
            : this(persistenceType, true)
        { }

        public DomainModelTestFixture(DataSourcePersistenceType persistenceType, bool buildModelWithConstraints)
            : this(persistenceType, buildModelWithConstraints, true)
        { }

        public DomainModelTestFixture(DataSourcePersistenceType persistenceType, bool buildModelWithConstraints, bool createNewDataStore)
        {
            m_PersistenceType = persistenceType;
            m_BuildModelWithConstraints = buildModelWithConstraints;
            m_CreateNewDataStore = createNewDataStore;
        }

        public DataSourcePersistenceType PersistenceType
        {
            get { return m_PersistenceType; }
        }

        public bool BuildModelWithConstraints
        {
            get { return m_BuildModelWithConstraints; }
        }

        public bool CreateNewDataStore
        {
            get { return m_CreateNewDataStore; }
        }

        public DOMAIN_MODEL DomainModel
        {
            get { return m_DomainModel; }
        }

        protected abstract DOMAIN_MODEL CreateDomainModel(DataSourcePersistenceType persistenceType, bool buildModelWithConstraints);

        [SetUp]
        public virtual void SetUp()
        {
            m_DomainModel = CreateDomainModel(PersistenceType, BuildModelWithConstraints);

            if (CreateNewDataStore)
            {
                if (PersistenceType == DataSourcePersistenceType.Database)
                {
                    DbContext dbContext = DomainModel.DataProvider.DataSource as DbContext;
                    bool dbDeleted = false;

                    bool dbAlreadyExists = dbContext.Database.Exists();
                    if (dbAlreadyExists)
                    {
                        dbDeleted = dbContext.Database.Delete();
                        Assert.IsTrue(dbDeleted);
                    }

                    dbContext.Database.Create();

                    bool dbExists = dbContext.Database.Exists();
                    Assert.IsTrue(dbExists);

                    m_DomainModel = CreateDomainModel(PersistenceType, BuildModelWithConstraints);
                }
            }
        }

        [TearDown]
        public virtual void TearDown()
        {
            m_DomainModel = null;
        }

        protected virtual void SaveAndReloadDomainModel()
        {
            DataSourcePersistenceType persistenceType = DomainModel.DataProvider.PersistenceType;

            if (persistenceType == DataSourcePersistenceType.Database)
            {
                // LATER: Verify that this code re-initializes the database connection only when no transaction is happening
                if (Transaction.Current == null)
                {
                    DbContext dbContext = DomainModel.DataProvider.DataSource as DbContext;
                    dbContext.Database.Initialize(true);

                    // LATER: Actually re-create DbContext
                    DomainModel.DataProvider.DataSource = DomainModel.CreateDataSource(persistenceType);
                }
            }
            else if (persistenceType == DataSourcePersistenceType.File)
            {
                object dataSource = null;
                object testDataSource = null;
                IBatchPersistableDomainModel persistableDomainModel = DomainModel as IBatchPersistableDomainModel;

                dataSource = persistableDomainModel.CreateDataSource(persistenceType);
                persistableDomainModel.PersistToDataSource(dataSource);

                DOMAIN_MODEL secondDomainModel = CreateDomainModel(DomainModel.PersistenceType, DomainModel.BuildModelWithConstraints);
                IBatchPersistableDomainModel secondDomainModelAsBatchPersistable = secondDomainModel as IBatchPersistableDomainModel;
                testDataSource = secondDomainModelAsBatchPersistable.CreateDataSource(persistenceType);
                secondDomainModelAsBatchPersistable.LoadFromDataSource(dataSource);
                secondDomainModelAsBatchPersistable.PersistToDataSource(testDataSource);

                foreach (IConstraint firstConstraint in DomainModel.Constraints)
                {
                    foreach (IConstraint secondConstraint in secondDomainModel.Constraints)
                    {
                        if (!firstConstraint.GetType().Equals(secondConstraint.GetType()))
                        { continue; }
                        secondConstraint.Enabled = firstConstraint.Enabled;
                    }
                }
                m_DomainModel = secondDomainModel;

                DataSet dataSourceAsDataSet = dataSource as DataSet;
                DataSet testDataSourceAsDataSet = testDataSource as DataSet;

                foreach (DataTable dataTable in dataSourceAsDataSet.Tables)
                {
                    DataTable testDataTable = testDataSourceAsDataSet.Tables[dataTable.TableName];

                    foreach (DataRow dataRow in dataTable.Rows)
                    {
                        DataRow foundDataRow = null;
                        foreach (DataRow testDataRow in testDataTable.Rows)
                        {
                            bool areEqual = true;

                            for (int i = 0; i < dataTable.Columns.Count; i++)
                            {
                                if (!dataRow[i].Equals(testDataRow[i]))
                                {
                                    areEqual = false;
                                    break;
                                }
                            }

                            if (areEqual)
                            {
                                foundDataRow = testDataRow;
                                break;
                            }
                        }

                        Assert.AreNotSame(dataRow, foundDataRow);
                        Assert.IsNotNull(foundDataRow);
                        //Assert.AreEqual(dataRow, foundDataRow);
                    }

                    Assert.AreNotSame(dataTable, testDataTable);
                    //Assert.AreEqual(dataTable, testDataTable);
                }

                Assert.AreNotSame(dataSource, testDataSource);
                //Assert.AreEqual(dataSource, testDataSource);
            }
        }
    }
}